#include "IWFFileMesh.h"
#include "..\Libs\libIWF\libIWF.h"
#include "..\Libs\libIWF\iwfFile.h"
#include "..\Libs\libIWF\iwfObjects.h"
#include "IWFFileParser.h"
#include "IWFMeshStructure.h"
#include "GraphicsController.h"


///////////////////////////////////////////////////////////////////////////////

IWFFileMesh::IWFFileMesh(CGraphicsController& graphicsController,
                         const std::wstring& fileName)
      : m_graphicsController(graphicsController),
      m_vertexBuffer(NULL)
{
   CFileIWF sceneFile;
   LPCTSTR strFileName = fileName.c_str();
   sceneFile.Load(strFileName);

   IWFFileParser parser(graphicsController, sceneFile);
   parser.processTextures(m_textures);
   parser.processMaterials(m_materials);

   processMeshes(sceneFile);

   calculateBoundingBox();

   buildBuffers();

   sceneFile.ClearObjects();
}

///////////////////////////////////////////////////////////////////////////////

IWFFileMesh::~IWFFileMesh(void)
{
   for (UINT i = 0; i < m_textures.size(); ++i)
   {
      m_textures[i]->Release();
   }
   m_textures.clear();

   for (UINT i = 0; i < m_textureGroups.size(); i++)
   {
      delete m_textureGroups[i];
   }
   m_textureGroups.clear();

   for (UINT i = 0; i < m_vertices.size(); i++)
   {
      delete m_vertices[i];
   }
   m_vertices.clear();

   if (m_vertexBuffer != NULL)
   {
      m_vertexBuffer->Release();
      m_vertexBuffer = NULL;
   }
}

///////////////////////////////////////////////////////////////////////////////

void IWFFileMesh::render()
{
   if (m_vertexBuffer == NULL) {return;}

   m_graphicsController.getD3Device().SetStreamSource(0, m_vertexBuffer, 0, sizeof(CIWFMeshVertex));

   for (UINT textureGroupIdx = 0; textureGroupIdx < m_textureGroups.size(); ++textureGroupIdx)
   {
      CTextureGroup* textureGroup = m_textureGroups[textureGroupIdx];
      textureGroup->render(m_graphicsController.getD3Device(), m_textures, m_materials);
   }
}

///////////////////////////////////////////////////////////////////////////////

void IWFFileMesh::processMeshes(const CFileIWF& sceneFile)
{
   for (long textureIdx = -1; textureIdx < (signed)(m_textures.size()); ++textureIdx)
   {
      for (long materialIdx = -1; materialIdx < (signed)(m_materials.size()); ++materialIdx)
      {
         for (UINT meshIdx = 0; meshIdx < sceneFile.m_vpMeshList.size(); ++meshIdx)
         {
            iwfMesh* mesh = sceneFile.m_vpMeshList[meshIdx];

            for (ULONG surfaceIdx = 0; surfaceIdx < mesh->SurfaceCount; ++surfaceIdx)
            {
               iwfSurface* surface = mesh->Surfaces[surfaceIdx];

               // determine the indices of the material and a texture as we loaded them into our structures
               UINT fileMaterialIdx = -1;
               UINT fileTextureIdx = -1;
               if ((surface->Components & SCOMPONENT_MATERIALS) && surface->ChannelCount > 0)
               {
                  fileMaterialIdx = surface->MaterialIndices[0];
               }

               if ((surface->Components & SCOMPONENT_TEXTURES) && surface->ChannelCount > 0)
               {
                  fileTextureIdx = surface->TextureIndices[0];
               }

               // if this surface uses both this material and this texture, memorize it for further processing
               if ((fileTextureIdx == textureIdx) && (fileMaterialIdx == materialIdx))
               {
                  CTextureGroup* textureGroup = findTextureGroup(textureIdx); 
                  if (textureGroup == NULL)
                  {
                     textureGroup = new CTextureGroup(m_graphicsController, textureIdx);
                     m_textureGroups.push_back(textureGroup);
                  }

                  CMaterialGroup* materialGroup = textureGroup->findMaterialGroup(materialIdx); 
                  if (materialGroup == NULL)
                  {
                     materialGroup = new CMaterialGroup(m_graphicsController, materialIdx, m_vertices.size());
                     textureGroup->addMaterialGroup(materialGroup);
                  }

                  materialGroup->addSurface(surface, m_vertices.size());

                  // add vertices
                  for (UINT i = 0; i < surface->VertexCount; i++)
                  {
                     m_vertices.push_back(new CIWFMeshVertex(surface->Vertices[i].x,
                        surface->Vertices[i].y,
                        surface->Vertices[i].z,
                        surface->Vertices[i].Normal.x,
                        surface->Vertices[i].Normal.y,
                        surface->Vertices[i].Normal.z,
                        surface->Vertices[i].TexCoords[0][0],
                        surface->Vertices[i].TexCoords[0][1]));
                  }
               }
            }
         }
      }
   } 
}

///////////////////////////////////////////////////////////////////////////////

void IWFFileMesh::buildBuffers()
{
   buildVertexBuffer();

   // build the buffers for all the texture groups
   for (UINT textureGroupIdx = 0; textureGroupIdx < m_textureGroups.size(); ++textureGroupIdx)
   {
      CTextureGroup* textureGroup = m_textureGroups[textureGroupIdx];
      textureGroup->buildBuffers();
   }
}

///////////////////////////////////////////////////////////////////////////////

void IWFFileMesh::buildVertexBuffer()
{
   if (m_vertexBuffer != NULL)
   {
      m_vertexBuffer->Release();
   }

   m_vertexBuffer = m_graphicsController.createVertexBuffer(sizeof(CIWFMeshVertex) * m_vertices.size(),
                                               D3DUSAGE_WRITEONLY,
                                               CIWFMeshVertex::FVF,
                                               D3DPOOL_MANAGED);
 
   CIWFMeshVertex* pVertex = NULL;
   HRESULT res = m_vertexBuffer->Lock(0, sizeof(CIWFMeshVertex) * m_vertices.size(), 
                                      (void**)&pVertex, D3DLOCK_DISCARD);
   if (FAILED(res))
   {
      throw std::logic_error(
         std::string("Cannot lock a light group vertex buffer"));
   }

   for (UINT i = 0; i < m_vertices.size(); i++)
   {
      *pVertex++ = *(m_vertices[i]);
   }

   m_vertexBuffer->Unlock();
}

///////////////////////////////////////////////////////////////////////////////

CTextureGroup* IWFFileMesh::findTextureGroup(long textureIdx)
{
   CTextureGroup* grp = NULL;

   for (ULONG i = 0; i < m_textureGroups.size(); ++i)
   {
      if (*(m_textureGroups[i]) == textureIdx)
      {
         grp = m_textureGroups[i];
         break;
      }
   }

   return grp;
}

////////////////////////////////////////////////////////////////////////////////

void IWFFileMesh::calculateBoundingBox()
{
   m_boundingBox.min.x = m_boundingBox.min.y = m_boundingBox.min.z = 99999999.f;
   m_boundingBox.max.x = m_boundingBox.max.y = m_boundingBox.max.z = -99999999.f;

   for (UINT i = 0; i < m_vertices.size(); ++i)
   {
      if (m_boundingBox.min.x > m_vertices[i]->m_coords.x) m_boundingBox.min.x = m_vertices[i]->m_coords.x;
      if (m_boundingBox.max.x < m_vertices[i]->m_coords.x) m_boundingBox.max.x = m_vertices[i]->m_coords.x;
      
      if (m_boundingBox.min.y > m_vertices[i]->m_coords.y) m_boundingBox.min.y = m_vertices[i]->m_coords.y;
      if (m_boundingBox.max.y < m_vertices[i]->m_coords.y) m_boundingBox.max.y = m_vertices[i]->m_coords.y;

      if (m_boundingBox.min.z > m_vertices[i]->m_coords.z) m_boundingBox.min.z = m_vertices[i]->m_coords.z;
      if (m_boundingBox.max.z < m_vertices[i]->m_coords.z) m_boundingBox.max.z = m_vertices[i]->m_coords.z;
   }
}

////////////////////////////////////////////////////////////////////////////////